/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Reviewed:
	Copyright (c) Microsoft Corporation

	Windows base application class.

***************************************************************************/
#include "frame.h"
ASSERTNAME


WIG vwig;

/***************************************************************************
	WinMain for any frame work app. Sets up vwig and calls FrameMain.
***************************************************************************/
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR pszs, int wShow)
{
	vwig.hinst = hinst;
	vwig.hinstPrev = hinstPrev;
	vwig.pszCmdLine = GetCommandLine();
	vwig.wShow = wShow;
	vwig.lwThreadMain = LwThreadCur();

	FrameMain();
	return 0;
}


/***************************************************************************
	Shutdown immediately.
***************************************************************************/
void APPB::Abort(void)
{
	_ShutDownViewer();
	FatalAppExit(0, PszLit("Fatal Error Termination"));
}


/***************************************************************************
	Do OS specific initialization.
***************************************************************************/
bool APPB::_FInitOS(void)
{
	AssertThis(0);
	STN stnApp;
	PSZ pszAppWndCls = PszLit("APP");

	// get the app name
	GetStnAppName(&stnApp);

	// register the window classes
	if (vwig.hinstPrev == hNil)
		{
		WNDCLASS wcs;

		wcs.style = CS_BYTEALIGNCLIENT | CS_OWNDC;
		wcs.lpfnWndProc = _LuWndProc;
		wcs.cbClsExtra = 0;
		wcs.cbWndExtra = 0;
		wcs.hInstance = vwig.hinst;
		wcs.hIcon = LoadIcon(NULL, IDI_APPLICATION);
		wcs.hCursor = LoadCursor(NULL, IDC_ARROW);
		wcs.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		wcs.lpszMenuName = 0;
		wcs.lpszClassName = pszAppWndCls;
		if (!RegisterClass(&wcs))
			return fFalse;

		wcs.lpfnWndProc = _LuMdiWndProc;
		wcs.lpszClassName = PszLit("MDI");
		if (!RegisterClass(&wcs))
			return fFalse;
		}

	if ((vwig.hwndApp = CreateWindow(pszAppWndCls, stnApp.Psz(),
			WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
			CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
			hNil, hNil, vwig.hinst, pvNil)) == hNil)
		{
		return fFalse;
		}
	if (hNil == (vwig.hdcApp = GetDC(vwig.hwndApp)))
		return fFalse;

	//set a timer, so we can idle regularly.
	if (SetTimer(vwig.hwndApp, 0, 1, pvNil) == 0)
		return fFalse;

	vwig.haccel = LoadAccelerators(vwig.hinst, MIR(acidMain));
	ShowWindow(vwig.hwndApp, vwig.wShow);
	return fTrue;
}


/***************************************************************************
	Get the next event from the OS event queue. Return true iff it's a
	real event (not just an idle type event).
***************************************************************************/
bool APPB::_FGetNextEvt(PEVT pevt)
{
	AssertThis(0);
	AssertVarMem(pevt);

	GetMessage(pevt, hNil, 0, 0);
	switch (pevt->message)
		{
	case WM_TIMER:
		return fFalse;

	case WM_MOUSEMOVE:
		// dispatch these so real Windows controls can receive them,
		// but return false so we can do our idle stuff - including
		// our own mouse moved stuff.
		_DispatchEvt(pevt);
		return fFalse;
		}

	return fTrue;
}


/***************************************************************************
	The given GOB is tracking the mouse. See if there are any relevant
	mouse events in the system event queue. Fill in *ppt with the location
	of the mouse relative to pgob. Also ensure that GrfcustCur() will
	return the correct mouse state.
***************************************************************************/
void APPB::TrackMouse(PGOB pgob, PT *ppt)
{
	AssertThis(0);
	AssertPo(pgob, 0);
	AssertVarMem(ppt);

	EVT evt;
	PTS pts;

	for (;;)
		{
		if (!PeekMessage(&evt, hNil, 0, 0, PM_REMOVE | PM_NOYIELD))
			{
			GetCursorPos(&pts);
			break;
			}

		if (FIn(evt.message, WM_MOUSEFIRST, WM_MOUSELAST + 1))
			{
			pts = evt.pt;
			break;
			}

		//toss key events
		if (!FIn(evt.message, WM_KEYFIRST, WM_KEYLAST + 1))
			{
			TranslateMessage(&evt);
			DispatchMessage(&evt);
			}
		}

	ppt->xp = pts.x;
	ppt->yp = pts.y;
	pgob->MapPt(ppt, cooGlobal, cooLocal);
}


/***************************************************************************
	Dispatch an OS level event to someone that knows what to do with it.
***************************************************************************/
void APPB::_DispatchEvt(PEVT pevt)
{
	AssertThis(0);
	AssertVarMem(pevt);

	CMD cmd;

	if (hNil != vwig.hwndClient &&
			TranslateMDISysAccel(vwig.hwndClient, pevt) ||
		hNil != vwig.haccel &&
			TranslateAccelerator(vwig.hwndApp, vwig.haccel, pevt))
		{
		return;
		}

	switch (pevt->message)
		{
	case WM_KEYDOWN:
	case WM_CHAR:
		if (_FTranslateKeyEvt(pevt, (PCMD_KEY)&cmd) && pvNil != vpcex)
			vpcex->EnqueueCmd(&cmd);
		ResetToolTip();
		break;

	case WM_KEYUP:
	case WM_DEADCHAR:
	case WM_SYSKEYDOWN:
	case WM_SYSKEYUP:
	case WM_SYSCHAR:
	case WM_SYSDEADCHAR:
		ResetToolTip();
		// fall thru
	default:
		TranslateMessage(pevt);
		DispatchMessage(pevt);
		break;
		}
}


/***************************************************************************
	Translate an OS level key down event to a CMD. This returns false if
	the key maps to a menu item.
***************************************************************************/
bool APPB::_FTranslateKeyEvt(PEVT pevt, PCMD_KEY pcmd)
{
	AssertThis(0);
	AssertVarMem(pevt);
	AssertVarMem(pcmd);

	EVT evt;

	ClearPb(pcmd, size(*pcmd));
	pcmd->cid = cidKey;

	if (pevt->message == WM_KEYDOWN)
		{
		TranslateMessage(pevt);
		if (PeekMessage(&evt, pevt->hwnd, 0, 0, PM_NOREMOVE) &&
			WM_CHAR == evt.message &&
			PeekMessage(&evt, pevt->hwnd, WM_CHAR, WM_CHAR, PM_REMOVE))
			{
			Assert(evt.message == WM_CHAR, 0);
			pcmd->ch = evt.wParam;
			}
		else
			pcmd->ch = chNil;
		pcmd->vk = pevt->wParam;
		}
	else
		{
		pcmd->vk = vkNil;
		pcmd->ch = pevt->wParam;
		}
	pcmd->grfcust = GrfcustCur();
	pcmd->cact = SwLow(pevt->lParam);

	return fTrue;
}


/***************************************************************************
	Look at the next system event and if it's a key, fill in the *pcmd with
	the relevant info.
***************************************************************************/
bool APPB::FGetNextKeyFromOsQueue(PCMD_KEY pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	EVT evt;

	for (;;)
		{
		if (!PeekMessage(&evt, hNil, 0, 0, PM_NOREMOVE) ||
			!FIn(evt.message, WM_KEYFIRST, WM_KEYLAST + 1) ||
			!PeekMessage(&evt, evt.hwnd, evt.message, evt.message, PM_REMOVE))
			{
			break;
			}

		if (hNil != vwig.hwndClient &&
				TranslateMDISysAccel(vwig.hwndClient, &evt) ||
			hNil != vwig.haccel &&
				TranslateAccelerator(vwig.hwndApp, vwig.haccel, &evt))
			{
			break;
			}

		switch (evt.message)
			{
		case WM_CHAR:
		case WM_KEYDOWN:
			if (!_FTranslateKeyEvt(&evt, pcmd))
				goto LFail;
			return fTrue;

		default:
			TranslateMessage(&evt);
			DispatchMessage(&evt);
			break;
			}
		}

LFail:
	TrashVar(pcmd);
	return fFalse;
}


/***************************************************************************
	Flush user generated events from the system event queue.
***************************************************************************/
void APPB::FlushUserEvents(ulong grfevt)
{
	AssertThis(0);
	EVT evt;

	while ((grfevt & fevtMouse) &&
			PeekMessage(&evt, hNil, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) ||
		(grfevt & fevtKey) &&
			PeekMessage(&evt, hNil, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE))
		{
		}
}


/***************************************************************************
	Get our app window out of the clipboard viewer chain.
***************************************************************************/
void APPB::_ShutDownViewer(void)
{
	if (vwig.hwndApp != hNil)
		ChangeClipboardChain(vwig.hwndApp, vwig.hwndNextViewer);
	vwig.hwndNextViewer = hNil;
}


/***************************************************************************
	Main window procedure (a static method).
***************************************************************************/
LRESULT CALLBACK APPB::_LuWndProc(HWND hwnd, uint wm, WPARAM wParam, LPARAM lw)
{
	AssertNilOrPo(vpappb, 0);
	long lwRet;

	if (pvNil != vpappb &&
			vpappb->_FFrameWndProc(hwnd, wm, wParam, lw, &lwRet))
		{
		return lwRet;
		}

	return DefFrameProc(hwnd, vwig.hwndClient, wm, wParam, lw);
}


/***************************************************************************
	Handle Windows messages for the main app window. Return true iff the
	default window proc should _NOT_ be called.
***************************************************************************/
bool APPB::_FFrameWndProc(HWND hwnd, uint wm,
	WPARAM wParam, LPARAM lw, long *plwRet)
{
	AssertThis(0);
	AssertVarMem(plwRet);

	PGOB pgob;
	RC rc;
	PT pt;
	long xp, yp;
	long lwT;
	long lwStyle;

	*plwRet = 0;
	switch (wm)
		{
	default:
		return _FCommonWndProc(hwnd, wm, wParam, lw, plwRet);

	case WM_CREATE:
		Assert(vwig.hwndApp == hNil, 0);
		vwig.hwndNextViewer = SetClipboardViewer(hwnd);
		vwig.hwndApp = hwnd;
		return fTrue;

	case WM_CHANGECBCHAIN:
		if ((HWND)wParam == vwig.hwndNextViewer)
			vwig.hwndNextViewer = (HWND)lw;
		else if (hNil != vwig.hwndNextViewer)
			SendMessage(vwig.hwndNextViewer, wm, wParam, lw);
		return fTrue;

	case WM_DRAWCLIPBOARD:
		if (hNil != vwig.hwndNextViewer)
			SendMessage(vwig.hwndNextViewer, wm, wParam, lw);
		if (vwig.hwndApp != hNil && GetClipboardOwner() != vwig.hwndApp)
			vpclip->Import();
		return fTrue;

	case WM_DESTROY:
		_ShutDownViewer();
		vwig.hwndApp = hNil;
		PostQuitMessage(0);
		return fTrue;

	case WM_SIZE:
		// make sure the style bits are set correctly
		lwT = lwStyle = GetWindowLong(vwig.hwndApp, GWL_STYLE);
		if (_fFullScreen && wParam == SIZE_MAXIMIZED)
			{
			// in full screen mode, set popup and nuke the system menu stuff
			lwStyle |= WS_POPUP;
			lwStyle &= ~(WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
			}
		else
			{
			// in non-full screen mode, clear popup and set the system menu stuff
			lwStyle &= ~WS_POPUP;
			lwStyle |= (WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
			}
		if (lwT != lwStyle)
			SetWindowLong(vwig.hwndApp, GWL_STYLE, lwStyle);

		return _FCommonWndProc(hwnd, wm, wParam, lw, plwRet);

	case WM_PALETTECHANGED:
		if ((HWND)wParam == hwnd)
			return fTrue;
		// fall thru
	case WM_QUERYNEWPALETTE:
		*plwRet = GPT::CclrSetPalette(hwnd, fTrue) > 0;
		return fTrue;

	case WM_GETMINMAXINFO:
		BLOCK
			{
			long dypFrame, dypScreen, dypExtra;
			MINMAXINFO *pmmi;

			pmmi = (MINMAXINFO *)lw;

			*plwRet = DefFrameProc(hwnd, vwig.hwndClient, wm, wParam, (long)pmmi);
			dypFrame = GetSystemMetrics(SM_CYFRAME);
			dypScreen = GetSystemMetrics(SM_CYSCREEN);
			dypExtra = 0;

			FGetProp(kpridFullScreen, &lw);
			if (lw)
				dypExtra = GetSystemMetrics(SM_CYCAPTION);
			pmmi->ptMaxPosition.y = -dypFrame - dypExtra;
			pmmi->ptMaxSize.y = pmmi->ptMaxTrackSize.y =
				dypScreen + 2 * dypFrame + dypExtra;
			_FCommonWndProc(hwnd, wm, wParam, (long)pmmi, &lw);
			}
		return fTrue;

	case WM_CLOSE:
		if (pvNil != vpcex)
			vpcex->EnqueueCid(cidQuit);
		return fTrue;

	case WM_QUERYENDSESSION:
		if (!_fQuit)
			Quit(fFalse);
		*plwRet = _fQuit;
		return fTrue;

	case WM_COMMAND:
		if (GET_WM_COMMAND_HWND(wParm, lw) != hNil)
			break;

		lwT = GET_WM_COMMAND_ID(wParam, lw);
		if (!FIn(lwT, wcidMinApp, wcidLimApp))
			break;

		if (pvNil != vpmubCur)
			vpmubCur->EnqueueWcid(lwT);
		else if (pvNil != vpcex)
			vpcex->EnqueueCid(lwT);
		return fTrue;

	case WM_INITMENU:
		if (vpmubCur != pvNil)
			{
			vpmubCur->Clean();
			return fTrue;
			}
		break;

	// these are for automated testing support...
	case WM_GOB_STATE:
		if (pvNil != (pgob = GOB::PgobFromHidScr(lw)))
			*plwRet = pgob->LwState();
		return fTrue;

	case WM_GOB_LOCATION:
		*plwRet = -1;
		if (pvNil == (pgob = GOB::PgobFromHidScr(lw)))
			return fTrue;

		pgob->GetRcVis(&rc, cooLocal);
		if (rc.FEmpty())
			return fTrue;
		pt.xp = pt.yp = 0;
		pgob->MapPt(&pt, cooLocal, cooGlobal);
		for (lwT = 0; lwT < 256; lwT++)
			{
			for (yp = rc.ypTop + (lwT & 0x0F); yp < rc.ypBottom; yp += 16)
				{
				for (xp = rc.xpLeft + (lwT >> 4); xp < rc.xpRight; xp += 16)
					{
					if (pgob->FPtIn(xp, yp) &&
						pgob == GOB::PgobFromPtGlobal(xp + pt.xp, yp + pt.yp))
						{
						pt.xp += xp;
						pt.yp += yp;
						GOB::PgobScreen()->MapPt(&pt, cooGlobal, cooLocal);
						*plwRet = LwHighLow((short)pt.xp, (short)pt.yp);
						return fTrue;
						}
					}
				}
			}
		return fTrue;

	case WM_GLOBAL_STATE:
		*plwRet = GrfcustCur();
		return fTrue;

	case WM_CURRENT_CURSOR:
		if (pvNil != _pcurs)
			*plwRet = _pcurs->Cno();
		else
			*plwRet = cnoNil;
		return fTrue;

	case WM_GET_PROP:
		if (!FGetProp(lw, plwRet))
			*plwRet = wParam;
		return fTrue;

	case WM_SCALE_TIME:
		*plwRet = vpusac->LuScale();
		vpusac->Scale(lw);
		return fTrue;

	case WM_GOB_FROM_PT:
		pt.xp = wParam;
		pt.yp = lw;
		GOB::PgobScreen()->MapPt(&pt, cooLocal, cooGlobal);
		if (pvNil != (pgob = GOB::PgobFromPtGlobal(pt.xp, pt.yp)))
			*plwRet = pgob->Hid();
		return fTrue;

	case WM_FIRST_CHILD:
		if (pvNil != (pgob = GOB::PgobFromHidScr(lw)) &&
				pvNil != (pgob = pgob->PgobFirstChild()))
			{
			*plwRet = pgob->Hid();
			}
		return fTrue;

	case WM_NEXT_SIB:
		if (pvNil != (pgob = GOB::PgobFromHidScr(lw)) &&
				pvNil != (pgob = pgob->PgobNextSib()))
			{
			*plwRet = pgob->Hid();
			}
		return fTrue;

	case WM_PARENT:
		if (pvNil != (pgob = GOB::PgobFromHidScr(lw)) &&
				pvNil != (pgob = pgob->PgobPar()))
			{
			*plwRet = pgob->Hid();
			}
		return fTrue;

	case WM_GOB_TYPE:
		if (pvNil != (pgob = GOB::PgobFromHidScr(lw)))
			*plwRet = pgob->Cls();
		return fTrue;

	case WM_IS_GOB:
		if (pvNil != (pgob = GOB::PgobFromHidScr(lw)))
			*plwRet = pgob->FIs(wParam);
		return fTrue;
		}

	return fFalse;
}


/***************************************************************************
	MDI window proc (a static method).
***************************************************************************/
LRESULT CALLBACK APPB::_LuMdiWndProc(HWND hwnd, uint wm,
	WPARAM wParam, LPARAM lw)
{
	AssertNilOrPo(vpappb, 0);
	long lwRet;

	if (pvNil != vpappb &&
			vpappb->_FMdiWndProc(hwnd, wm, wParam, lw, &lwRet))
		{
		return lwRet;
		}

	return DefMDIChildProc(hwnd, wm, wParam, lw);
}


/***************************************************************************
	Handle MDI window messages. Returns true iff the default window proc
	should _NOT_ be called.
***************************************************************************/
bool APPB::_FMdiWndProc(HWND hwnd, uint wm,
	WPARAM wParam, LPARAM lw, long *plwRet)
{
	AssertThis(0);
	AssertVarMem(plwRet);

	PGOB pgob;
	long lwT;

	*plwRet = 0;
	switch (wm)
		{
	default:
		return _FCommonWndProc(hwnd, wm, wParam, lw, plwRet);

	case WM_GETMINMAXINFO:
		*plwRet = DefMDIChildProc(hwnd, wm, wParam, lw);
		_FCommonWndProc(hwnd, wm, wParam, lw, &lwT);
		return fTrue;

	case WM_CLOSE:
		if ((pgob = GOB::PgobFromHwnd(hwnd)) != pvNil)
			vpcex->EnqueueCid(cidCloseWnd, pgob);
		return fTrue;

	case WM_MDIACTIVATE:
		GOB::ActivateHwnd(hwnd,
			GET_WM_MDIACTIVATE_FACTIVATE(hwnd, wParam, lw));
		break;
		}

	return fFalse;
}


/***************************************************************************
	Common stuff between the two window procs. Returns true if the default
	window proc should _NOT_ be called.
***************************************************************************/
bool APPB::_FCommonWndProc(HWND hwnd, uint wm,
	WPARAM wParam, LPARAM lw, long *plwRet)
{
	AssertThis(0);
	AssertVarMem(plwRet);

	PGOB pgob;
	PT pt;
	PSCB pscb;
	RC rc;
	HDC hdc;
	PAINTSTRUCT ps;
	HRGN hrgn;

	*plwRet = 0;
	switch (wm)
		{
	case WM_PAINT:
		if (IsIconic(hwnd))
			break;

		// make sure the palette is selected and realized....
		// theoretically, we shouldn't have to do this, but because
		// of past and present Win bugs, we do it to be safe.
		GPT::CclrSetPalette(hwnd, fFalse);

		// invalidate stuff that we have marked internally (may as well
		// draw everything that needs drawn).
		InvalMarked(hwnd);

		// NOTE: BeginPaint has a bug where it returns in ps.rcPaint the
		// bounds of the update region intersected with the current clip region.
		// This causes us to not draw everything we need to. To fix this we
		// save, open up, and restore the clipping region around the BeginPaint
		// call.
		hdc = GetDC(hwnd);
		if (hNil == hdc)
			goto LFailPaint;

		if (FCreateRgn(&hrgn, pvNil) && 1 != GetClipRgn(hdc, hrgn))
			FreePhrgn(&hrgn);
		SelectClipRgn(hdc, hNil);

		if (!BeginPaint(hwnd, &ps))
			{
			ReleaseDC(hwnd, hdc);
LFailPaint:
			Warn("Painting failed");
			break;
			}

		// Since we use CS_OWNDC, these DCs should be the same...
		Assert(hdc == ps.hdc, 0);

		rc = RC(ps.rcPaint);
		UpdateHwnd(hwnd, &rc);
		EndPaint(hwnd, &ps);

		// don't call the default window proc - or it will clear anything
		// that got invalidated while we were drawing (which can happen
		// in a multi-threaded pre-emptive environment).
		return fTrue;

	case WM_SYSCOMMAND:
		if (wParam == SC_SCREENSAVE && !FAllowScreenSaver())
			return fTrue;
		break;

	case WM_GETMINMAXINFO:
		if (pvNil != (pgob = GOB::PgobFromHwnd(hwnd)))
			{
			MINMAXINFO *pmmi = (MINMAXINFO far *)lw;

			pgob->GetMinMax(&rc);
			pmmi->ptMinTrackSize.x =
				LwMax(pmmi->ptMinTrackSize.x, rc.xpLeft);
			pmmi->ptMinTrackSize.y =
				LwMax(pmmi->ptMinTrackSize.y, rc.ypTop);
			pmmi->ptMaxTrackSize.x =
				LwMin(pmmi->ptMaxTrackSize.x, rc.xpRight);
			pmmi->ptMaxTrackSize.y =
				LwMin(pmmi->ptMaxTrackSize.y, rc.ypBottom);
			}
		return fTrue;

	case WM_SIZE:
		if (pvNil != (pgob = GOB::PgobFromHwnd(hwnd)))
			pgob->SetRcFromHwnd();
		break;

	case WM_LBUTTONDOWN:
	case WM_LBUTTONDBLCLK:
		ResetToolTip();
		if (pvNil != (pgob = GOB::PgobFromHwnd(hwnd)) &&
				pvNil != (pgob = pgob->PgobFromPt(SwLow(lw), SwHigh(lw), &pt)))
			{
			long ts;

			// compute the multiplicity of the click - don't use Windows'
			// guess, since it can be wrong for our GOBs. It's even wrong
			// at the HWND level! (Try double-clicking the maximize button).
			ts = GetMessageTime();
			if (_pgobMouse == pgob &&
					FIn(ts - _tsMouse, 0, GetDoubleClickTime()))
				{
				_cactMouse++;
				}
			else
				_cactMouse = 1;
			_tsMouse = ts;
			if (_pgobMouse != pgob && pvNil != _pgobMouse)
				{
				AssertPo(_pgobMouse, 0);
				vpcex->EnqueueCid(cidRollOff, _pgobMouse);
				}
			_pgobMouse = pgob;
			_xpMouse = klwMax;
			pgob->MouseDown(pt.xp, pt.yp, _cactMouse, GrfcustCur());
			}
		else
			_pgobMouse = pvNil;
		break;

	case WM_LBUTTONUP:
	case WM_RBUTTONDOWN:
	case WM_RBUTTONUP:
	case WM_RBUTTONDBLCLK:
	case WM_MBUTTONDOWN:
	case WM_MBUTTONUP:
	case WM_MBUTTONDBLCLK:
		ResetToolTip();
		break;

	case WM_SETCURSOR:
		if (LOWORD(lw) != HTCLIENT)
			return fFalse;
		RefreshCurs();
		return fTrue;

	case WM_HSCROLL:
	case WM_VSCROLL:
		pscb = (PSCB)CTL::PctlFromHctl(GET_WM_HSCROLL_HWND(wParam, lw));
		if (pvNil != pscb && pscb->FIs(kclsSCB))
			{
			pscb->TrackScroll(GET_WM_HSCROLL_CODE(wParam, lw),
				GET_WM_HSCROLL_POS(wParam, lw));
			}
		break;

	case WM_ACTIVATEAPP:
		_Activate(FPure(wParam));
		break;
		}

	return fFalse;
}


#ifdef DEBUG
/***************************************************************************
	Debug initialization.
***************************************************************************/
bool APPB::_FInitDebug(void)
{
	AssertThis(0);
	return fTrue;
}


// passes the strings to the assert dialog proc
STN *_rgpstn[3];

/***************************************************************************
	Dialog proc for assert.
***************************************************************************/
BOOL CALLBACK _FDlgAssert(HWND hdlg, UINT msg, WPARAM w, LPARAM lw)
{
	switch (msg)
		{
	case WM_INITDIALOG:
		SetDlgItemText(hdlg, 3, _rgpstn[0]->Psz());
		SetDlgItemText(hdlg, 4, _rgpstn[1]->Psz());
		SetDlgItemText(hdlg, 5, _rgpstn[2]->Psz());
		return fTrue;

	case WM_COMMAND:
		switch (GET_WM_COMMAND_ID(w, lw))
			{
		default:
			break;

		case 0:
		case 1:
		case 2:
			EndDialog(hdlg, GET_WM_COMMAND_ID(w, lw));
			return fTrue;
			}
		break;
		}
	return fFalse;
}


MUTX _mutxAssert;

/***************************************************************************
	The assert proc. Returning true breaks into the debugger.
***************************************************************************/
bool APPB::FAssertProcApp(PSZS pszsFile, long lwLine, PSZS pszsMsg,
	void *pv, long cb)
{
	const long kclwChain = 10;
	STN stn0, stn1, stn2;
	int tmc;
	PSZ psz;
	long cact;
	long *plw;
	long ilw;
	long rglw[kclwChain];

	_mutxAssert.Enter();

	if (_fInAssert)
		{
		_mutxAssert.Leave();
		return fFalse;
		}

	_fInAssert = fTrue;

	_rgpstn[0] = &stn0;
	_rgpstn[1] = &stn1;
	_rgpstn[2] = &stn2;

	// build the main assert message with file name and line number
	if (pszsMsg == pvNil || *pszsMsg == 0)
		psz = PszLit("Assert (%s line %d)");
	else
		{
		psz = PszLit("Assert (%s line %d): %s");
		stn2.SetSzs(pszsMsg);
		}
	if (pvNil != pszsFile)
		stn1.SetSzs(pszsFile);
	else
		stn1 = PszLit("Some Header file");
	stn0.FFormatSz(psz, &stn1, lwLine, &stn2);

	// call stack - follow the EBP chain....
	__asm { mov plw,ebp }
	for (ilw = 0; ilw < kclwChain; ilw++)
		{
		if (pvNil == plw || IsBadReadPtr(plw, 2 * size(long)) ||
				*plw <= (long)plw)
			{
			rglw[ilw] = 0;
			plw = pvNil;
			}
		else
			{
			rglw[ilw] = plw[1];
			plw = (long *)*plw;
			}
		}

	for (cact = 0; cact < 2; cact++)
		{
		// format data
		if (pv != pvNil && cb > 0)
			{
			byte *pb = (byte *)pv;
			long cbT = cb;
			long ilw;
			long lw;
			STN stnT;

			stn2.SetNil();
			for (ilw = 0; ilw < 20 && cb >= 4; cb -= 4, pb += 4, ++ilw)
				{
				CopyPb(pb, &lw, 4);
				stnT.FFormatSz(PszLit("%08x "), lw);
				stn2.FAppendStn(&stnT);
				}
			if (ilw < 20 && cb > 0)
				{
				lw = 0;
				CopyPb(pb, &lw, cb);
				stnT.FFormatSz((cb <= 2) ? PszLit("%04x") : PszLit("%08x"), lw);
				stn2.FAppendStn(&stnT);
				}
			}
		else
			stn2.SetNil();

		if (cact == 0)
			{
			pv = rglw;
			cb = size(rglw);
			stn1 = stn2;
			}
		}

	OutputDebugString(stn0.Psz());
	OutputDebugString(PszLit("\n"));

	if (stn1.Cch() > 0)
		{
		OutputDebugString(stn1.Psz());
		OutputDebugString(PszLit("\n"));
		}
	if (stn2.Cch() > 0)
		{
		OutputDebugString(stn2.Psz());
		OutputDebugString(PszLit("\n"));
		}

	if (LwThreadCur() != vwig.lwThreadMain)
		{
		// can't use a dialog - it may cause grid - lock
		long sid;
		ulong grfmb;

		stn0.FAppendSz(PszLit("\n"));
		stn0.FAppendStn(&stn1);
		stn0.FAppendSz(PszLit("\n"));
		stn0.FAppendStn(&stn2);

		grfmb = MB_SYSTEMMODAL | MB_YESNO | MB_ICONHAND;
		sid = MessageBox(hNil, stn0.Psz(),
			PszLit("Thread Assert! (Y = Ignore, N = Debugger)"), grfmb);

		switch (sid)
			{
		default:
			tmc = 0;
			break;
		case IDNO:
			tmc = 1;
			break;
			}
		}
	else
		{
		// run the dialog
		tmc = DialogBox(vwig.hinst, PszLit("AssertDlg"), vwig.hwndApp,
			&_FDlgAssert);
		}

	_fInAssert = fFalse;
	_mutxAssert.Leave();

	switch (tmc)
		{
	case 0:
		// ignore
		return fFalse;

	case 1:
		// break into debugger
		return fTrue;

	case 2:
		// abort
		Abort(); // shouldn't return
		Debugger();
		break;
		}

	return fFalse;
}
#endif //DEBUG


/***************************************************************************
	Put an alert up. Return which button was hit. Returns tYes for yes
	or ok; tNo for no; tMaybe for cancel.
***************************************************************************/
bool APPB::TGiveAlertSz(PSZ psz, long bk, long cok)
{
	AssertThis(0);
	AssertSz(psz);

	long sid;
	ulong grfmb;
	HWND hwnd;

	grfmb = MB_APPLMODAL;
	switch (bk)
		{
	default:
		BugVar("bad bk value", &bk);
		//fall through
	case bkOk:
		grfmb |= MB_OK;
		break;
	case bkOkCancel:
		grfmb |= MB_OKCANCEL;
		break;
	case bkYesNo:
		grfmb |= MB_YESNO;
		break;
	case bkYesNoCancel:
		grfmb |= MB_YESNOCANCEL;
		break;
		}

	switch (cok)
		{
	default:
		BugVar("bad cok value", &cok);
		//fall through
	case cokNil:
		break;
	case cokInformation:
		grfmb |= MB_ICONINFORMATION;
		break;
	case cokQuestion:
		grfmb |= MB_ICONQUESTION;
		break;
	case cokExclamation:
		grfmb |= MB_ICONEXCLAMATION;
		break;
	case cokStop:
		grfmb |= MB_ICONSTOP;
		break;
		}

	hwnd = GetActiveWindow();
	if (hNil == hwnd)
		hwnd = vwig.hwndApp;
	sid = MessageBox(hwnd, psz, PszLit(""), grfmb);

	switch (sid)
		{
	default:
	case IDYES:
	case IDOK:
		return tYes;
	case IDCANCEL:
		return tMaybe;
	case IDNO:
		return tNo;
		}
}

